package service

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

	"github.com/ammyhaber/seckill/pkg/errcode"
	"github.com/ammyhaber/seckill/service/inventory/cache"
	"github.com/ammyhaber/seckill/service/inventory/dao"
	"github.com/ammyhaber/seckill/service/inventory/model"
)

// InventoryLockService 库存锁定服务
type InventoryLockService struct {
	inventoryDAO     *dao.InventoryDAO
	inventoryLockDAO *dao.InventoryLockDAO
	redisCache       *cache.RedisCache
}

// NewInventoryLockService 创建库存锁定服务
func NewInventoryLockService(
	inventoryDAO *dao.InventoryDAO,
	inventoryLockDAO *dao.InventoryLockDAO,
	redisCache *cache.RedisCache,
) *InventoryLockService {
	return &InventoryLockService{
		inventoryDAO:     inventoryDAO,
		inventoryLockDAO: inventoryLockDAO,
		redisCache:       redisCache,
	}
}

// LockInventory 锁定库存（第一阶段预扣减）
func (s *InventoryLockService) LockInventory(ctx context.Context, req *model.InventoryLockRequest) (bool, error) {
	if req.OrderID == "" || req.ProductID <= 0 || req.Count <= 0 {
		return false, errors.New("参数无效")
	}

	// 1. 检查是否已存在锁定记录
	existingLock, err := s.inventoryLockDAO.GetLockByOrderAndProduct(req.OrderID, req.ProductID)
	if err != nil {
		return false, fmt.Errorf("检查锁定记录失败: %w", err)
	}

	if existingLock != nil {
		// 已经有锁定记录，根据状态返回不同结果
		switch existingLock.Status {
		case model.LockStatusLocked:
			// 已锁定，不需要重复操作
			return true, nil
		case model.LockStatusConfirmed:
			// 已确认，不能重复锁定
			return false, errors.New("库存已确认扣减，不能重复锁定")
		case model.LockStatusCanceled:
			// 已取消，可以重新锁定(后续处理)
		}
	}

	// 2. 获取分布式锁，确保并发安全
	lockValue, err := s.redisCache.AcquireLock(ctx, req.ProductID)
	if err != nil {
		return false, fmt.Errorf("获取分布式锁失败: %w", err)
	}
	defer s.redisCache.ReleaseLock(ctx, req.ProductID, lockValue)

	// 3. 从数据库获取最新库存信息
	detail, err := s.inventoryDAO.GetInventoryDetailByProductID(req.ProductID)
	if err != nil {
		return false, fmt.Errorf("获取库存信息失败: %w", err)
	}

	if detail == nil {
		return false, errors.New("商品不存在")
	}

	// 4. 检查库存是否足够
	if detail.Available < req.Count {
		return false, errors.New(errcode.GetMsg(errcode.InventoryNotEnough))
	}

	// 5. 开始数据库事务
	tx, err := s.inventoryDAO.GetDB().BeginTxx(ctx, nil)
	if err != nil {
		return false, fmt.Errorf("开始事务失败: %w", err)
	}

	// 确保事务结束
	defer func() {
		if r := recover(); r != nil {
			_ = tx.Rollback()
			panic(r)
		}
	}()

	// 6. 更新数据库中的总库存（乐观锁更新）
	newVersion := int64(detail.Version) + 1
	rowsAffected, err := s.inventoryDAO.ExecUpdateInventoryWithTx(
		tx,
		detail.Available-req.Count,
		detail.Locked+req.Count,
		newVersion,
		req.ProductID,
		int64(detail.Version),
	)
	if err != nil {
		_ = tx.Rollback()
		return false, fmt.Errorf("更新库存失败: %w", err)
	}

	if rowsAffected == 0 {
		_ = tx.Rollback()
		return false, errors.New("库存已被修改，请重试")
	}

	// 7. 创建锁定记录
	err = s.inventoryLockDAO.CreateLockWithTx(tx, req.OrderID, req.ProductID, req.Count)
	if err != nil {
		_ = tx.Rollback()
		return false, fmt.Errorf("创建锁定记录失败: %w", err)
	}

	// 8. 提交事务
	if err := tx.Commit(); err != nil {
		return false, fmt.Errorf("提交事务失败: %w", err)
	}

	// 9. 更新Redis缓存
	inventory := &model.Inventory{
		ProductID: req.ProductID,
		Available: detail.Available - req.Count,
		Locked:    detail.Locked + req.Count,
		Version:   int(newVersion),
	}
	_ = s.redisCache.SetInventory(ctx, inventory)

	return true, nil
}

// ConfirmInventoryDeduction 确认库存扣减（第二阶段确认扣减）
func (s *InventoryLockService) ConfirmInventoryDeduction(ctx context.Context, req *model.InventoryLockConfirmRequest) error {
	if req.OrderID == "" || req.ProductID <= 0 {
		return errors.New("参数无效")
	}

	// 1. 检查锁定记录
	lock, err := s.inventoryLockDAO.GetLockByOrderAndProduct(req.OrderID, req.ProductID)
	if err != nil {
		return fmt.Errorf("获取锁定记录失败: %w", err)
	}

	if lock == nil {
		return errors.New("找不到锁定记录")
	}

	if lock.Status != model.LockStatusLocked {
		// 非锁定状态，说明已经确认或取消
		if lock.Status == model.LockStatusConfirmed {
			// 已确认，幂等返回成功
			return nil
		}
		return errors.New("库存锁定记录状态错误，无法确认扣减")
	}

	// 2. 获取分布式锁，确保并发安全
	lockValue, err := s.redisCache.AcquireLock(ctx, req.ProductID)
	if err != nil {
		return fmt.Errorf("获取分布式锁失败: %w", err)
	}
	defer s.redisCache.ReleaseLock(ctx, req.ProductID, lockValue)

	// 3. 开始数据库事务
	tx, err := s.inventoryDAO.GetDB().BeginTxx(ctx, nil)
	if err != nil {
		return fmt.Errorf("开始事务失败: %w", err)
	}

	// 确保事务结束
	defer func() {
		if r := recover(); r != nil {
			_ = tx.Rollback()
			panic(r)
		}
	}()

	// 4. 更新数据库中的锁定记录状态
	err = s.inventoryLockDAO.ConfirmLockWithTx(tx, req.OrderID, req.ProductID)
	if err != nil {
		_ = tx.Rollback()
		return fmt.Errorf("更新锁定记录状态失败: %w", err)
	}

	// 5. 更新数据库中的库存
	err = s.inventoryDAO.ConfirmDeductionByProductIDWithTx(tx, req.ProductID, lock.Count)
	if err != nil {
		_ = tx.Rollback()
		return fmt.Errorf("确认扣减库存失败: %w", err)
	}

	// 6. 提交事务
	if err := tx.Commit(); err != nil {
		return fmt.Errorf("提交事务失败: %w", err)
	}

	// 7. 更新Redis缓存
	// 先获取当前缓存状态
	inventory, err := s.redisCache.GetInventory(ctx, req.ProductID)
	if err == nil && inventory != nil {
		// 更新缓存
		inventory.Locked -= lock.Count
		_ = s.redisCache.SetInventory(ctx, inventory)
	}

	return nil
}

// CancelInventoryDeduction 取消库存扣减（回滚操作）
func (s *InventoryLockService) CancelInventoryDeduction(ctx context.Context, req *model.InventoryLockCancelRequest) error {
	if req.OrderID == "" || req.ProductID <= 0 {
		return errors.New("参数无效")
	}

	// 1. 检查锁定记录
	lock, err := s.inventoryLockDAO.GetLockByOrderAndProduct(req.OrderID, req.ProductID)
	if err != nil {
		return fmt.Errorf("获取锁定记录失败: %w", err)
	}

	if lock == nil {
		return errors.New("找不到锁定记录")
	}

	if lock.Status != model.LockStatusLocked {
		// 非锁定状态，说明已经确认或取消
		if lock.Status == model.LockStatusCanceled {
			// 已取消，幂等返回成功
			return nil
		}
		if lock.Status == model.LockStatusConfirmed {
			return errors.New("库存已确认扣减，无法取消")
		}
	}

	// 2. 获取分布式锁，确保并发安全
	lockValue, err := s.redisCache.AcquireLock(ctx, req.ProductID)
	if err != nil {
		return fmt.Errorf("获取分布式锁失败: %w", err)
	}
	defer s.redisCache.ReleaseLock(ctx, req.ProductID, lockValue)

	// 3. 开始数据库事务
	tx, err := s.inventoryDAO.GetDB().BeginTxx(ctx, nil)
	if err != nil {
		return fmt.Errorf("开始事务失败: %w", err)
	}

	// 确保事务结束
	defer func() {
		if r := recover(); r != nil {
			_ = tx.Rollback()
			panic(r)
		}
	}()

	// 4. 更新数据库中的锁定记录状态
	err = s.inventoryLockDAO.CancelLockWithTx(tx, req.OrderID, req.ProductID)
	if err != nil {
		_ = tx.Rollback()
		return fmt.Errorf("更新锁定记录状态失败: %w", err)
	}

	// 5. 更新数据库中的库存
	err = s.inventoryDAO.UnlockByProductIDWithTx(tx, req.ProductID, lock.Count)
	if err != nil {
		_ = tx.Rollback()
		return fmt.Errorf("取消库存锁定失败: %w", err)
	}

	// 6. 提交事务
	if err := tx.Commit(); err != nil {
		return fmt.Errorf("提交事务失败: %w", err)
	}

	// 7. 更新Redis缓存
	// 先获取当前缓存状态
	inventory, err := s.redisCache.GetInventory(ctx, req.ProductID)
	if err == nil && inventory != nil {
		// 更新缓存
		inventory.Available += lock.Count
		inventory.Locked -= lock.Count
		_ = s.redisCache.SetInventory(ctx, inventory)
	}

	return nil
}

// GetInventoryLockDetail 获取库存锁定详情
func (s *InventoryLockService) GetInventoryLockDetail(ctx context.Context, orderID string, productID int64) (*model.InventoryLock, error) {
	return s.inventoryLockDAO.GetLockByOrderAndProduct(orderID, productID)
}

// ProcessExpiredLocks 处理超时未确认的锁定记录（定时任务调用）
func (s *InventoryLockService) ProcessExpiredLocks(ctx context.Context, expireDuration time.Duration) error {
	expireTime := time.Now().Add(-expireDuration)
	locks, err := s.inventoryLockDAO.GetExpiredLocks(expireTime)
	if err != nil {
		return fmt.Errorf("获取超时锁定记录失败: %w", err)
	}

	for _, lock := range locks {
		// 对每个超时的锁定记录进行取消操作
		req := &model.InventoryLockCancelRequest{
			OrderID:   lock.OrderID,
			ProductID: lock.ProductID,
		}
		if err := s.CancelInventoryDeduction(ctx, req); err != nil {
			log.Printf("取消超时锁定记录失败: orderID=%s, productID=%d, error=%v\n",
				lock.OrderID, lock.ProductID, err)
			// 继续处理下一个，不中断流程
		}
	}

	return nil
}

// syncInventoryToDB 同步Redis库存到数据库
func (s *InventoryLockService) syncInventoryToDB(ctx context.Context, productID int64) error {
	// 获取Redis中的库存
	inventory, err := s.redisCache.GetInventory(ctx, productID)
	if err != nil {
		return fmt.Errorf("获取Redis库存失败: %w", err)
	}

	// 获取数据库中的库存
	dbInventory, err := s.inventoryDAO.GetInventoryDetailByProductID(productID)
	if err != nil {
		return fmt.Errorf("获取数据库库存失败: %w", err)
	}

	// 使用乐观锁更新数据库
	rowsAffected, err := s.inventoryDAO.ExecUpdateInventory(
		inventory.Available,
		inventory.Locked,
		int64(dbInventory.Version)+1,
		productID,
		int64(dbInventory.Version),
	)
	if err != nil {
		return fmt.Errorf("更新数据库库存失败: %w", err)
	}

	if rowsAffected == 0 {
		// 乐观锁更新失败，说明库存已被其他事务修改
		// 这里可以选择重试或者其他策略
		return errors.New("乐观锁更新失败，数据库库存已被修改")
	}

	return nil
}
